package top.yangguangmc.sunshine_anticheat.check.checks.world;

import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import top.yangguangmc.sunshine_anticheat.check.Check;
import top.yangguangmc.sunshine_anticheat.check.CheckCategory;
import top.yangguangmc.sunshine_anticheat.check.CheckSetting;
import top.yangguangmc.sunshine_anticheat.events.ServerTickEvent;
import top.yangguangmc.sunshine_anticheat.utils.Utils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class FastBreakCheck extends Check {
    private final Map<UUID, Boolean> isPlayerDigging = new HashMap<>();
    private final Map<UUID, Long> startDiggingTime = new HashMap<>();
    @CheckSetting(configName = "ticks-faster-than-expected")
    private int threshold = 1;

    public FastBreakCheck() {
        super("FastBreak", "fast-break", CheckCategory.WORLD);
    }

    @Override
    public void onPacketReceive(PacketReceiveEvent event) {
        Player player = (Player) event.getPlayer();
        if (!validateCheckable(player)) return;
        if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
            WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
            if (packet.getAction() != DiggingAction.START_DIGGING && packet.getAction() != DiggingAction.CANCELLED_DIGGING && packet.getAction() != DiggingAction.FINISHED_DIGGING)
                return;
            Block block = new Location(player.getWorld(), packet.getBlockPosition().getX(), packet.getBlockPosition().getY(),
                    packet.getBlockPosition().getZ()).getBlock();
            switch (packet.getAction()) {
                case START_DIGGING:
                    float hardness = 0;
                    try {
                        Method getNMSBlock = Utils.getCraftClass("block.CraftBlock").getDeclaredMethod("getNMSBlock");
                        getNMSBlock.setAccessible(true);
                        Object nmsBlock = getNMSBlock.invoke(block);
                        hardness = (Float) Utils.getNMSClass("Block").getDeclaredField("strength").get(nmsBlock);
                    } catch (Exception ignored) {
                    }
                    if (isPlayerDigging.getOrDefault(player.getUniqueId(), false) && hardness > 0) {
                        failCheck(player, 2, "Bad digging packet: Duplicated START_DIGGING packet.");
                    }
                    isPlayerDigging.put(player.getUniqueId(), true);
                    startDiggingTime.put(player.getUniqueId(), ServerTickEvent.tickId);
                    break;
                case CANCELLED_DIGGING:
                    if (!isPlayerDigging.getOrDefault(player.getUniqueId(), true)) {
                        failCheck(player, 3, "Bad digging packet: Duplicated CANCELLED_DIGGING packet.");
                    }
                    isPlayerDigging.put(player.getUniqueId(), false);
                    break;
                case FINISHED_DIGGING:  //TODO: 存在误报
                    if (!isPlayerDigging.getOrDefault(player.getUniqueId(), true) && !block.getType().hasGravity()) {
                        failCheck(player, 1, "Bad digging packet: Duplicated FINISHED_DIGGING packet.");
                    }
                    long took = ServerTickEvent.tickId - startDiggingTime.getOrDefault(player.getUniqueId(), 0L);
                    int expected = getTicksExpected(player, block);
                    if (took < expected - threshold && !player.getLocation().getBlock().isLiquid()) {
                        failCheck(player, expected - took > 5 ? 2 : 1,
                                "Tried to break the block faster than expected. Block: {}[{}, {}, {}], took: {}, expected: {}",
                                block.getType(), block.getX(), block.getY(), block.getZ(), took, expected);
                    }
                    isPlayerDigging.put(player.getUniqueId(), false);
                    break;
            }
        }
    }

    private int getTicksExpected(Player player, Block block) {
        try {
            Class<?> nmsClass = Utils.getNMSClass("Block");
            Object nmsPlayer = Utils.getCraftClass("entity.CraftPlayer").getDeclaredMethod("getHandle").invoke(player);
            Object nmsWorld = Utils.getCraftClass("CraftWorld").getDeclaredMethod("getHandle").invoke(player.getWorld());
            Method getNMSBlock = Utils.getCraftClass("block.CraftBlock").getDeclaredMethod("getNMSBlock");
            getNMSBlock.setAccessible(true);
            Object nmsBlock = getNMSBlock.invoke(block);
            Object nmsBlockPos = Utils.getNMSClass("BlockPosition").getConstructor(int.class, int.class, int.class).newInstance(block.getX(), block.getY(), block.getZ());
            float damage = (Float) nmsClass.getDeclaredMethod("getDamage", Utils.getNMSClass("EntityHuman"), Utils.getNMSClass("World"), Utils.getNMSClass("BlockPosition")).invoke(nmsBlock, nmsPlayer, nmsWorld, nmsBlockPos);
            if (damage > 0) {
                return (int) Math.floor(1 / damage);
            } else return 0;
        } catch (Exception e) {
            return -1;
        }
    }
}
